﻿using AlphaD.Core.Functions;
using AlphaD.Core.Strategies;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AlphaD.Core
{
    public class LinearDiffEq2DiscontBvpStrategy : ILinearDiffEq2DiscontBvpStrategy
    {
        private readonly ILinearDiffEq2BvpStrategy _bvpStrategy;

        public LinearDiffEq2DiscontBvpStrategy(ILinearDiffEq2BvpStrategy bvpStrategy)
        {
            this._bvpStrategy = bvpStrategy;
        }

        public IEnumerable<LinearDiffEq2BvpFunc> Resolve(LinearDiffEq2DiscontBvp problem, SolutionDetails solutionDetails = null)
        {
            int n = problem.Disconts.Count;                        
            LinearDiffEq2BvpFunc[] solutions = new LinearDiffEq2BvpFunc[n + 1];

            #region Iteration 0

            LinearDiffEq2[] eq0 = CreateEquestions(problem, false);
            LinearDiffEq2Bvp[] subproblems = new LinearDiffEq2Bvp[n + 1];

            subproblems[0] = new LinearDiffEq2Bvp
            (
                eq0[0],
                problem.LeftCondition,
                BoundaryCondition.CreateSecond(problem.Disconts[0], problem.K[0], 0)
            );

            for (int i = 1; i < n; i++)
            {
                subproblems[i] = new LinearDiffEq2Bvp
                (
                    eq0[i],                    
                    BoundaryCondition.CreateSecond(problem.Disconts[i - 1], problem.K[i], 0),
                    BoundaryCondition.CreateSecond(problem.Disconts[i], problem.K[i], 0)
                );                
            }

            subproblems[n] = new LinearDiffEq2Bvp
            (
                eq0[n],
                BoundaryCondition.CreateSecond(problem.Disconts[n - 1], problem.K[n], 0),
                problem.RightCondition
            );
            
            for(int i = 0; i <= n; i++)
            {
                solutions[i] = this._bvpStrategy.Resolve(subproblems[i]);
            }

            this.StoreIterationDetails(solutionDetails, 0, problem, solutions);

            #endregion Iteration 0

            #region Iteraton >= 1

            LinearDiffEq2[] eqn = CreateEquestions(problem, true);

            for (int iteration = 1; iteration <= 10; iteration++)
            {                
                subproblems[0] = new LinearDiffEq2Bvp
                (
                    eqn[0],
                    BoundaryCondition.CreateFirst(problem.LeftCondition.X, 0),
                    BoundaryCondition.CreateSecond(problem.Disconts[0], problem.K[0], subproblems[0].RightCondition.Value - problem.Alpha[0] * (solutions[1].Value(problem.Disconts[0]) - solutions[0].Value(problem.Disconts[0])))
                );

                for (int i = 1; i < n; i++)
                {
                    subproblems[i] = new LinearDiffEq2Bvp
                    (
                        eqn[i],
                        BoundaryCondition.CreateSecond(problem.Disconts[i - 1], problem.K[i], subproblems[i].LeftCondition.Value - problem.Alpha[i - 1] * (solutions[i].Value(problem.Disconts[i - 1]) - solutions[i - 1].Value(problem.Disconts[i - 1]))),
                        BoundaryCondition.CreateSecond(problem.Disconts[i], problem.K[i], subproblems[i].RightCondition.Value - problem.Alpha[i] * (solutions[i + 1].Value(problem.Disconts[i]) - solutions[i].Value(problem.Disconts[i])))
                    );
                }

                subproblems[n] = new LinearDiffEq2Bvp
                (
                    eqn[n],
                    BoundaryCondition.CreateSecond(problem.Disconts[n - 1], problem.K[n], subproblems[n].LeftCondition.Value - problem.Alpha[n - 1] * (solutions[n].Value(problem.Disconts[n - 1]) - solutions[n - 1].Value(problem.Disconts[n - 1]))),
                    BoundaryCondition.CreateFirst(problem.RightCondition.X, 0)
                );

                for (int i = 0; i <= n; i++)
                {
                    solutions[i].AddTo(this._bvpStrategy.Resolve(subproblems[i]));
                }

                this.StoreIterationDetails(solutionDetails, iteration, problem, solutions);
            }

            #endregion Iteraton >= 1

            return solutions;
        }

        private LinearDiffEq2[] CreateEquestions(LinearDiffEq2DiscontBvp problem, bool zeroF)
        {
            LinearDiffEq2[] eqs = new LinearDiffEq2[problem.Disconts.Count + 1];
            for(int i = 0; i <= problem.Disconts.Count; i++)
            {
                eqs[i] = new LinearDiffEq2(problem.K[i], problem.R[i], (zeroF == false ? problem.F[i] : 0));
            }
            return eqs;
        }

        private void StoreIterationDetails(
            SolutionDetails solutionDetails, 
            int iterationNumber,
            LinearDiffEq2DiscontBvp problem, 
            LinearDiffEq2BvpFunc[] solutions)
        {
            if (solutionDetails == null)
            {
                return;
            }

            IterationDetails iterationDetails = new IterationDetails(iterationNumber);
            iterationDetails.Jumps = new double[problem.Disconts.Count];

            for(int i = 0; i < problem.Disconts.Count; i++)
            {
                iterationDetails.Jumps[i] = solutions[i + 1].Value(problem.Disconts[i] - solutions[i].Value(problem.Disconts[i]));
            }
            solutionDetails.AddIterationDetails(iterationDetails);
        }
    }
}
